package com.ai.cmdc.codeutil.java_generator;


import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.exception.ShellException;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import static org.mybatis.generator.internal.util.messages.Messages.getString;


public class ClassGeneratorPlugin {

    private List<GeneratorConfig.Service> entities;
    private FullyQualifiedJavaType listType;
    private FullyQualifiedJavaType service = new FullyQualifiedJavaType("org.springframework.stereotype.Service");
    private String nowDate;
    private FullyQualifiedJavaType serializable = new FullyQualifiedJavaType("java.io.Serializable");
    private FullyQualifiedJavaType lombok = new FullyQualifiedJavaType("lombok.Data");
    private FullyQualifiedJavaType restController = new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RestController");
    private FullyQualifiedJavaType apiAnnotationType = new FullyQualifiedJavaType("io.swagger.annotations.Api");
    private FullyQualifiedJavaType requestMapping = new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RequestMapping");
    private FullyQualifiedJavaType requestParam = new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RequestParam");
    private FullyQualifiedJavaType requestBody = new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RequestBody");
    private FullyQualifiedJavaType apiOperation = new FullyQualifiedJavaType("io.swagger.annotations.ApiOperation");
    private FullyQualifiedJavaType pathVariable = new FullyQualifiedJavaType("org.springframework.web.bind.annotation.PathVariable");
    private FullyQualifiedJavaType requestMethod = new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RequestMethod");
    private FullyQualifiedJavaType beanCovertor = new FullyQualifiedJavaType("com.ai.cmdc.common.utils.common.BeanConvertor");
    private FullyQualifiedJavaType comConsts = new FullyQualifiedJavaType("com.ai.cmdc.common.constants.CommonConst");
    private FullyQualifiedJavaType autowrid = new FullyQualifiedJavaType("org.springframework.beans.factory.annotation.Autowired");
    private String author;


    public ClassGeneratorPlugin(GeneratorConfig config) {
        this.entities = config.getServices();
        author = config.getAuthor();
    }

    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles() throws ShellException {
        List<GeneratedJavaFile> files = new ArrayList<>();
        for (GeneratorConfig.Service entity : entities) {
            listType = new FullyQualifiedJavaType("java.util.List");

            FullyQualifiedJavaType apiType = new FullyQualifiedJavaType(entity.getApiPack() + ".I" + entity.getServiceName() + "ApiService");
            FullyQualifiedJavaType controllerType = new FullyQualifiedJavaType(entity.getControllerPack() + "." + entity.getServiceName() + "Controller");
            FullyQualifiedJavaType serviceType = new FullyQualifiedJavaType(entity.getServicePack() + ".I" + entity.getServiceName() + "Service");
            FullyQualifiedJavaType serviceImplType = new FullyQualifiedJavaType(entity.getImplPack() + "." + entity.getServiceName() + "ServiceImpl");

            Interface apiInterface = new Interface(apiType);
            Interface serviceInterface = new Interface(serviceType);
            TopLevelClass controllerClass = new TopLevelClass(controllerType);
            TopLevelClass serviceImplClass = new TopLevelClass(serviceImplType);

            controllerClass.addSuperInterface(apiType);
            serviceImplClass.addSuperInterface(serviceType);
            controllerClass.addAnnotation("@RestController");
            serviceImplClass.addAnnotation("@Service");

            //添加方法
            //api接口
            Set<FullyQualifiedJavaType> dtoSets = addApi(apiInterface, entity, entity.getFunctions(), files);
            //controller
            addController(controllerClass, entity, entity.getFunctions(), files);
            //接口
            addService(serviceInterface, entity, entity.getFunctions(), files);
            //实现类
            addServiceImpl(serviceImplClass, entity, entity.getFunctions(), files);
            // 导入必要的类
            Set<FullyQualifiedJavaType> importSets = new HashSet<>(Arrays.asList(apiAnnotationType, apiOperation,
                    requestMapping, requestBody, pathVariable, requestParam, listType));
            //api
            apiInterface.addImportedTypes(importSets);
            apiInterface.addImportedTypes(dtoSets);
            apiInterface.addImportedType(requestMethod);

            //controller
            importSets = new HashSet<>(Arrays.asList(requestBody, pathVariable, requestParam, listType));
            controllerClass.addImportedTypes(importSets);
            controllerClass.addImportedType(apiType);
            controllerClass.addImportedType(serviceType);
            controllerClass.addImportedTypes(dtoSets);
            controllerClass.addImportedType(autowrid);
            controllerClass.addImportedType(restController);

            //controller增加注入字段
            Field field = new Field();
            field.setVisibility(JavaVisibility.PRIVATE);
            field.setName("i" + entity.getServiceName() + "Service");
            field.setType(serviceType);
            field.addAnnotation("@Autowired");
            controllerClass.addField(field);

            //service
            serviceInterface.addImportedTypes(importSets);
            serviceInterface.addImportedTypes(dtoSets);

            //serviceImpl
            serviceImplClass.addImportedTypes(importSets);
            serviceImplClass.addImportedType(serviceType);
            serviceImplClass.addImportedTypes(dtoSets);
            serviceImplClass.addImportedType(service);

            //添加类注释
            apiInterface.addJavaDocLine(getDocTmpl(author));
            serviceInterface.addJavaDocLine(getDocTmpl(author));
            controllerClass.addJavaDocLine(getDocTmpl(author));
            serviceImplClass.addJavaDocLine(getDocTmpl(author));
        }

        return files;
    }

    private Set<FullyQualifiedJavaType> addApi(Interface interface1, GeneratorConfig.Service service, List<GeneratorConfig.Function> functions, List<GeneratedJavaFile> files) throws ShellException {
        String serviceRemark = service.getServiceRemark();
        interface1.setVisibility(JavaVisibility.PUBLIC);
        interface1.addAnnotation("@Api(value = \"" + serviceRemark + "组合服务API接口\", description = \"" + serviceRemark + "组合服务API接口\")");
        interface1.addAnnotation("@RequestMapping(value = \"/" + service.getUrl() + "\")");
        Set<FullyQualifiedJavaType> set = new HashSet<>();
        for (GeneratorConfig.Function function : functions) {
            List<GeneratorConfig.Entity> iDtos = function.getIDTos();
            GeneratorConfig.Entity oDto = function.getODTo();
            if (oDto.isCreate()) {
                FullyQualifiedJavaType oDToType = createDto(interface1, files, oDto);
                set.add(oDToType);
            }
            for (GeneratorConfig.Entity iDto : iDtos) {
                if (iDto.isCreate()) {
                    FullyQualifiedJavaType iDToType = createDto(interface1, files, iDto);
                    set.add(iDToType);
                }
            }

            Method method = createFucntion(function, false, service.getServiceName(), 1);
            method.removeAllBodyLines();
            String functionName = service.getServicePack() + ".I" + service.getServiceRemark() + "ApiService";
            method.addJavaDocLine(getDocTmpl(functionName, "", oDto.entityName, function.getFunctionRemark()));
            method.addAnnotation("@ApiOperation(value = \"I" + service.getServiceName() + "ApiService\", notes = \"" + function.getFunctionRemark() + "\")");
            method.addAnnotation("@RequestMapping(value = \"/" + function.getUrl() + "\", method = RequestMethod." + function.getMethod() + ")");
            interface1.addMethod(method);
        }
        GeneratedJavaFile file = new GeneratedJavaFile(interface1, service.getTargetProject());
        files.add(file);
        return set;
    }

    private FullyQualifiedJavaType createDto(Interface interface1, List<GeneratedJavaFile> files, GeneratorConfig.Entity iDto) throws ShellException {
        FullyQualifiedJavaType iPoType = new FullyQualifiedJavaType(iDto.getEntityPack() + "." + iDto.getEntityName());
        File directory = getDirectory(iDto.getEntityTarget(), iDto.getEntityPack());
        File dtoFile = new File(directory, iDto.getEntityName());
        if (dtoFile.exists()) {
            System.out.println("入参类：" + dtoFile.getName() + "已存在");
        } else {
            GeneratedJavaFile file = createService(iPoType, iDto.getEntityTarget());
            files.add(file);
        }
        interface1.addImportedType(iPoType);
        return iPoType;
    }

    private void addController(TopLevelClass topLevelClass, GeneratorConfig.Service service, List<GeneratorConfig.Function> functions, List<GeneratedJavaFile> files) throws ShellException {
        topLevelClass.setVisibility(JavaVisibility.PUBLIC);
        for (GeneratorConfig.Function function : functions) {
            Method method = createFucntion(function, true, service.getServiceName(), 2);
            method.addAnnotation("@Override");
            topLevelClass.addMethod(method);
        }
        GeneratedJavaFile file = new GeneratedJavaFile(topLevelClass, service.getTargetProject());
        files.add(file);
    }

    /**
     * 添加接口
     */
    private void addService(Interface interface1, GeneratorConfig.Service service, List<GeneratorConfig.Function> functions, List<GeneratedJavaFile> files) throws ShellException {
        interface1.setVisibility(JavaVisibility.PUBLIC);
        for (GeneratorConfig.Function function : functions) {
            Method method = createFucntion(function, false, service.getServiceName(), 3);
            method.removeAllBodyLines();
            String functionName = service.getServicePack() + ".I" + service.getServiceRemark() + "Service";
            method.addJavaDocLine(getDocTmpl(functionName, "", function.getODTo().entityName, function.getFunctionRemark()));
            interface1.addMethod(method);
        }
        GeneratedJavaFile file = new GeneratedJavaFile(interface1, service.getTargetProject());
        files.add(file);
    }

    /**
     * 添加实现类
     */
    private void addServiceImpl(TopLevelClass topLevelClass, GeneratorConfig.Service service, List<GeneratorConfig.Function> functions, List<GeneratedJavaFile> files) {
        topLevelClass.setVisibility(JavaVisibility.PUBLIC);
        for (GeneratorConfig.Function function : functions) {
            Method method = createFucntion(function, true, service.getServiceRemark(), 4);
            method.addAnnotation("@Override");
            topLevelClass.addMethod(method);
        }
        GeneratedJavaFile file = new GeneratedJavaFile(topLevelClass, service.getTargetProject());
        files.add(file);
    }


    /**
     * @param function
     * @param flag
     * @param type:    1-api,2-controller,3-service,4-serviceImpl
     * @return
     */
    private Method createFucntion(GeneratorConfig.Function function, boolean flag, String serviceName, int type) {
        Method method = new Method();
        if (flag) {
            method.setVisibility(JavaVisibility.PUBLIC);
        }

        method.setName(function.getFunctionName());
        //添加出参
        GeneratorConfig.Entity oDTo = function.getODTo();
        FullyQualifiedJavaType oDToType = new FullyQualifiedJavaType(oDTo.getEntityPack() + "." + oDTo.getEntityName());
        if (oDTo.isList()) {
            oDToType = new FullyQualifiedJavaType("List<" + oDToType.getShortName() + ">");
        }
        method.setReturnType(oDToType);

        //添加入参
        StringBuilder params = new StringBuilder();
        List<GeneratorConfig.Entity> iDTos = function.getIDTos();
        for (GeneratorConfig.Entity iDTo : iDTos) {
            FullyQualifiedJavaType iDToType = new FullyQualifiedJavaType(iDTo.getEntityPack() + "." + iDTo.getEntityName());
            String paramName = iDTo.getParamName() != null ? iDTo.getParamName() : "inParam";
            Parameter parameter = new Parameter(iDToType, paramName);
            if (type == 1 || type == 2) {
                parameter.addAnnotation(iDTo.getAnnotionStr());
            }
            method.addParameter(parameter);
            if (params.length() > 0) {
                params.append(",");
            }
            params.append(paramName);
        }
        StringBuilder sb = new StringBuilder();
        if (!oDTo.getEntityName().equals("void")) {
            sb.append("return ");
        }
        if (type == 2) {
            sb.append("i").append(serviceName).append("Service.").append(function.getFunctionName());
            sb.append("(").append(params.toString()).append(");");
        } else {
            sb.append("null;");
        }
        method.addBodyLine(sb.toString());
        return method;
    }

    private GeneratedJavaFile createService(FullyQualifiedJavaType type, String target) {
        TopLevelClass ipoClass = new TopLevelClass(type);
        ipoClass.setVisibility(JavaVisibility.PUBLIC);
        ipoClass.addSuperInterface(serializable);
        ipoClass.addImportedType(serializable);
        ipoClass.addImportedType(lombok);
        ipoClass.addAnnotation("@Data");
        ipoClass.addJavaDocLine(getDocTmpl(author));
        Field field = new Field();
        field.setName("serialVersionUID"); // 设置变量名
        FullyQualifiedJavaType fieldType = new FullyQualifiedJavaType("long");
        field.setType(fieldType); // 类型
        field.setVisibility(JavaVisibility.PRIVATE);
        field.setFinal(true);
        field.setStatic(true);
        field.setInitializationString("1L");
        ipoClass.addField(field);
        return new GeneratedJavaFile(ipoClass, target);
    }

    private String getDocTmpl(String author) {
        byte[] bytes = {(byte) 0xC2, (byte) 0xA0};
        String space;
        space = new String(bytes, StandardCharsets.UTF_8);
        return "/**\n"
                + space + "* @author: " + author + "\n"
                + space + "* @Description: \n"
                + space + "* @date: " + now() + "\n"
                + space + " */";
    }

    /**
     * 导入需要的类
     */
    private void addImport(Interface interfaces, TopLevelClass topLevelClass, FullyQualifiedJavaType interfaceType) {

        interfaces.addImportedType(listType);
        topLevelClass.addImportedType(interfaceType);
        topLevelClass.addImportedType(listType);

    }

    private String now() {
        if (nowDate != null) {
            return nowDate;
        }
        Date now = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        nowDate = simpleDateFormat.format(now);
        return nowDate;
    }


    private File getDirectory(String targetProject, String targetPackage) throws ShellException {
        System.out.println(System.getProperty("user.dir"));
        File project = new File(targetProject);
        if (!project.isDirectory()) {
            throw new ShellException(getString("Warning.9", //$NON-NLS-1$
                    targetProject));
        }
        StringBuilder sb = new StringBuilder();
        StringTokenizer st = new StringTokenizer(targetPackage, "."); //$NON-NLS-1$
        while (st.hasMoreTokens()) {
            sb.append(st.nextToken());
            sb.append(File.separatorChar);
        }

        File directory = new File(project, sb.toString());
        if (!directory.isDirectory()) {
            boolean rc = directory.mkdirs();
            if (!rc) {
                throw new ShellException(getString("Warning.10", //$NON-NLS-1$
                        directory.getAbsolutePath()));
            }
        }
        return directory;
    }

    private String getDocTmpl(String func, String param, String back, String desc) {

        byte[] bytes = {(byte) 0xC2, (byte) 0xA0};
        String space;
        space = new String(bytes, StandardCharsets.UTF_8);
        space = space + space + space;
        return " "
                + "/** \n"
                + space + "* @Function: " + func + "\n"
                + space + "* @Description: " + desc + "\n"
                + space + "* @param: " + param + "\n"
                + space + "* @return: " + back + "\n"
                + space + "* @version: v1.0.0\n"
                + space + "* @author: " + author + "\n"
                + space + "* @date: " + now() + "\n"
                + space + "*\n"
                + space + "* Modification History:\n"
                + space + "* Date         author          Version            Description\n"
                + space + "*------------------------------------------------------*\n"
                + space + "* " + now() + "     " + author + "           v1.0.0             修改原因\n"
                + space + "*\n"
                + space + "*/";
    }

    protected String toLowerCase(String tableName) {
        StringBuilder sb = new StringBuilder(tableName);
        sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
        return sb.toString();
    }

}
